(ns packet.example.dlt645
  (:require [packet.core :as p :refer [defpacket defslots]]
            [packet.bytes :refer [size get-byte set-byte paste]]
            [packet.utils :refer [in? index-of]]
            [packet.types :refer [ubcd u hex]]
            [packet.example.types :refer
             [voltage current frequency factor
              power energy mhDMY mhDM]]))

(defn b32 [di] (bit-and (bit-shift-right di 16) 0xffff))
(defn b10 [di] (bit-and di 0xffff))
(defn b3 [di] (bit-and (bit-shift-right di 24) 0xff))
(defn b2 [di] (bit-and (bit-shift-right di 16) 0xff))
(defn b1 [di] (bit-and (bit-shift-right di 8) 0xff))
(defn b0 [di] (bit-and di 0xff))

;;控制字
(defslots control-word
  (bits 1
        dir (7 "传送方向" :desc ["下行" "上行"])
        err (6 "异常应答" :desc ["正常" "异常"])
        more (5 "后续帧标志" :desc ["无后续" "有后续"])
        fc (0 4 "功能码" :desc {0 "保留" 8 "广播校时" 17 "读数据" 18 "读后续数据"
                                19 "读通信地址" 20 "写数据" 21 "写通信地址"
                                22 "冻结命令" 23 "更改通信速率" 24 "修改密码"
                                25 "最大需量清零" 26 "电表清零" 27 "事件清零"})))
;;电表的负荷记录块
(defslots load-record
  0xa0
  0xa0
  (length-of-all -5 "负荷记录长度")
  time (mhDMY "时标")
  (option (not= 0xaa)
          ua (voltage "A相电压")
          ub (voltage "B相电压")
          uc (voltage "C相电压")
          ia (current "A相电流")
          ib (current "B相电流")
          ic (current "C相电流")
          f (frequency "频率"))
  0xaa
  (option (not= 0xaa)
          p (power "总有功功率") 
          pa (power "A相有功功率") 
          pb (power "B相有功功率") 
          pc (power "C相有功功率") 
          q(power "总无功功率") 
          qa (power "A相无功功率") 
          qb (power "B相无功功率") 
          qc (power "C相无功功率"))
  0xaa
  (option (not= 0xaa)
          pf (factor "总功率因数") 
          pfa (factor "A相功率因数") 
          pfb (factor "B相功率因数") 
          pfc (factor "C相功率因数"))
  0xaa
  (option (not= 0xaa)
          e1 (energy "正向有功总电量")
          e2 (energy "反向有功总电量")
          eq12 (energy "组合无功1总电量")
          eq34 (energy "组合无功2总电量"))
  0xaa
  (option (not= 0xaa)
          eq1 (energy "第一象限无功总电量")
          eq2 (energy "第二象限无功总电量")
          eq3 (energy "第三象限无功总电量")
          eq4 (energy "第四象限无功总电量"))
  0xaa
  (option (not= 0xaa)
          px (power "当前有功需量")
          qx (power "当前无功需量"))
  0xaa
  (checksum "校验码")
  0xe5)

;;负荷记录模式字
(defslots load-record-mode
  (bits 1
        uif (0 "电压、电流、频率")
        pq (1 "有、无功功率")
        pf (2 "功率因数")
        epq (3 "有、无功总电量")
        eq1234 (4 "四象限无功总电量")
        demand (5 "当前需量")))

;;冻结数据模式字
(defslots frozen-mode
  (bits 1
        zpe (0 "正向有功电能")
        fpe (1 "反向有功电能")
        q1 (2 "组合无功1电能")
        q2 (3 "组合无功2电能")
        q1234 (4 "四相限无功电能")
        fpx (5 "正向有功最大需量")
        fpx (6 "反向有功最大需量")
        va (7 "变量")))

(defslots data
  (break when (and (= (b3 di) 0x06)
                   (<= (b2 di) 6)
                   (= (b1 di) 0)
                   (<= (b0 di) 2))
         (slots load-record))
  (case di
    0x04000901 (slots load-record-mode "负荷记录模式字")
    0x04000902 (slots frozen-mode "定时冻结数据模式字")
    0x04000903 (slots frozen-mode "瞬时冻结数据模式字")
    0x04000904 (slots frozen-mode "约定冻结数据模式字")
    0x04000905 (slots frozen-mode "整点冻结数据模式字")
    0x04000906 (slots frozen-mode "日冻结数据模式字")
    0x04000a01 (start-time (mhDM "负荷记录起始时间"))
    0x04000a02 (gap1 (ubcd 2 "第一类负荷记录间隔时间(分)"))
    0x04000a03 (gap2 (ubcd 2 "第二类负荷记录间隔时间(分)"))
    0x04000a04 (gap3 (ubcd 2 "第三类负荷记录间隔时间(分)"))
    0x04000a05 (gap4 (ubcd 2 "第四类负荷记录间隔时间(分)"))
    0x04000a06 (gap5 (ubcd 2 "第五类负荷记录间隔时间(分)"))
    0x04000a07 (gap6 (ubcd 2 "第六类负荷记录间隔时间(分)"))
    
    0x06100101 (ua (voltage "A相电压"))
    0x06100102 (ub (voltage "B相电压"))
    0x06100103 (uc (voltage "C相电压"))
    0x061001ff (++ 0x06100101 - 0x06100103)
    
    0x06100201 (ia (current "A相电流"))
    0x06100202 (ib (current "B相电流"))
    0x06100203 (ic (current "C相电流"))
    0x061002ff (++ 0x06100201 - 0x06100203)
    
    0x06100300 (p (power "总有功功率"))
    0x06100301 (pa (power "A相有功功率"))
    0x06100302 (pb (power "B相有功功率"))
    0x06100303 (pc (power "C相有功功率"))
    0x061003ff (++ 0x06100300 - 0x06100303)
    
    0x06100400 (q (power "总无功功率"))
    0x06100401 (qa (power "A相无功功率"))
    0x06100402 (qb (power "B相无功功率"))
    0x06100403 (qc (power "C相无功功率"))
    0x061004ff (++ 0x06100400 - 0x06100403)
    
    0x06100500 (pf (factor "总功率因数"))
    0x06100501 (pfa (factor "A相功率因数"))
    0x06100502 (pfb (factor "B相功率因数"))
    0x06100503 (pfc (factor "C相功率因数"))
    0x061005ff (++ 0x06100500 - 0x06100503)
    
    0x06100601 (e1 (energy "正向有功总电量"))
    0x06100602 (e2 (energy "反向有功总电量"))
    0x06100603 (eq12 (energy "组合无功1总电量"))
    0x06100604 (eq34 (energy "组合无功2总电量"))
    0x061006ff (++ 0x06100601 - 0x06100604)
    
    0x06100701 (eq1 (energy "第一象限无功电量"))
    0x06100702 (eq2 (energy "第二象限无功电量"))
    0x06100703 (eq3 (energy "第三象限无功电量"))
    0x06100704 (eq4 (energy "第四象限无功电量"))
    0x061007ff (++ 0x06100701 - 0x06100704)
    
    0x06100801 (px (power "有功需量"))
    0x06100802 (qx (power "无功需量"))
    0x061008ff (++ 0x06100801 0x06100802)))

(defslots error
  (bits 1
        exceed-fees (6 "费率数超")
        exceed-day-segs (5 "日时段数超")
        exceed-year-segs (4 "年时区数超")
        comm-rate-fixed (3 "通信速率不能更改")
        wrong-psw (2 "密码错/未授权")
        no-data (1 "无请求数据")
        other-err (0 "其他错误")))

(defslots body
  (break when (= err 1) (slots error))
  (when (in? fc 3 17 18 20)
    di (hex 4 "数据标识"))
  (when (and (= dir 0) (= fc 17) (= (b3 di) 0x06))
    block-count (ubcd "负荷记录块数")
    (when (or (= (b32 di) 0x0610) (= (b0 di) 1))
      start-time (mhDMY "起始时间")))
  (when (and (= dir 1) (in? fc 17 18))
    (break when (= (b32 di) 0x0610)
           start-time (mhDMY "起始时间")
           data (repeat (< (+ &0 (if (= fc 18) 1 0))
                           (+ data-len &data-len 1))
                        (slots data)))
    (slots data))
  (when (= fc 18)
    fseq (u "帧序号")))

;;DLT645规约规定在发送和接收时对数据字段进行加减0x33的预处理
;;不在外部进行预处理是因为该报文可能要嵌入其他报文中
;;对报文的预处理不能更改原始数据，必须返回新的数据
;;新数据同样必须实现IRandomAccessBytes协议，建议返回一个向量
(defpacket dlt645
  {:prep (fn [bs start]
           (let [start (loop [i start]
                         (when (>= i (size bs))
                           (throw (ex-info "未发现起始标志0x68" nil)))
                         (if (= (get-byte bs i) 0x68)
                           i
                           (recur (inc i))))
                 len (get-byte bs (+ start 9))
                 nbs (loop [nbs (transient []) i 0]
                       (if (< i (size bs))
                         (recur (conj! nbs (get-byte bs i))
                                (inc i))
                         nbs))]
             (reduce (fn [nbs i]
                       (let [b (nth nbs (+ start i))
                             b (if (= i (+ 10 len))
                                 (- b (* 0x33 len))
                                 (- b 0x33))]
                         (assoc! nbs (+ start i) b)))
                     nbs
                     (range 10 (+ 10 len 1)))))
   
   :postp (fn [bs start]
            (let [len (get-byte bs (+ start 9))]
              (reduce (fn [bs i]
                        (let [b (get-byte bs (+ start i))
                              b (if (= i (+ 10 len))
                                  (+ b (* 0x33 len))
                                  (+ b 0x33))]
                          (set-byte bs (+ start i) b)))
                      bs
                      (range 10 (+ 10 len 1)))))}
  (skip-until 0x68 ? ? ? ? ? ? 0x68)
  addr (ubcd 6 "地址域")
  0x68
  (slots control-word)

  data-len (u "数据长度")
  (slots body :len data-len)
  ;;下面的表示法在解析结果中多了一个data层次
  ;;(length-of body)
  ;;data (slots body)

  (checksum "校验码")
  0x16)

